package edu.calvin.djs32.cs214.interpreters.calculator;

import static org.easymock.EasyMock.expect;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;

import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;

import edu.calvin.cs214.hobbes2008.ApplicationTIR;
import edu.calvin.cs214.hobbes2008.HobbesTIR;
import edu.calvin.cs214.hobbes2008.IdentifierTIR;
import edu.calvin.cs214.hobbes2008.IfTIR;
import edu.calvin.cs214.hobbes2008.IntegerTIR;
import edu.calvin.cs214.hobbes2008.LambdaTIR;
import edu.calvin.cs214.hobbes2008.LetFunctionTIR;
import edu.calvin.cs214.hobbes2008.LetTIR;
import edu.calvin.cs214.hobbes2008.StringTIR;
import edu.calvin.cs214.hobbes2008.TupleTIR;
import edu.calvin.cs214.hobbes2008.environments.IEnvironment;
import edu.calvin.djs32.cs214.interpreters.support.EasyMockControl;


public class CalculatorTest {
	private Calculator myCalculator;
	private EasyMockControl myControl;
	private IEnvironment<Applicable> myApplicables;
	private IEnvironment<HobbesTIR> myEnvironment;

	@SuppressWarnings("unchecked")
	@Before
	public void setUp() {
		myControl = new EasyMockControl();
		myEnvironment = myControl.createMock(IEnvironment.class);
		myApplicables = myControl.createMock(IEnvironment.class);
		myCalculator = new Calculator(myApplicables, myEnvironment);
	}
	
	@Test
	public void ShouldReturnIntegerUntouched(){
		assertEquals(new IntegerTIR(1), myCalculator.visitInteger(new IntegerTIR(1)));
		assertEquals(new IntegerTIR(+2), myCalculator.visitInteger(new IntegerTIR(+2)));
		assertEquals(new IntegerTIR(2), myCalculator.visitInteger(new IntegerTIR(2)));
		assertEquals(new IntegerTIR(0x123f), myCalculator.visitInteger(new IntegerTIR(0x123f)));
	}
	
	@Test
	public void ShouldReturnStringUntoucher(){
		assertEquals(new StringTIR("foo"), myCalculator.visitString(new StringTIR("foo")));
		assertEquals(new StringTIR("bar"), myCalculator.visitString(new StringTIR("bar")));
		assertEquals(new StringTIR("foo bar"), myCalculator.visitString(new StringTIR("foo bar")));
	}
	
	@Test
	public void ShouldReturnTupleUntouched(){
		HobbesTIR element1 = myControl.createMock(HobbesTIR.class);
		HobbesTIR element2 = myControl.createMock(HobbesTIR.class);
		HobbesTIR result1 = myControl.createMock(HobbesTIR.class);
		HobbesTIR result2 = myControl.createMock(HobbesTIR.class);
		expect(element1.accept(myCalculator)).andReturn(result1);
		expect(element2.accept(myCalculator)).andReturn(result2);
		myControl.replay();
		assertEquals(new TupleTIR(result1, result2), myCalculator.visitTuple(new TupleTIR(element1, element2)));
		myControl.verify();
	}
	
	@Test
	public void shouldCalculateApplication(){
		Applicable primitive = myControl.createMock(Applicable.class);
		HobbesTIR result = myControl.createMock(HobbesTIR.class);
		
		IdentifierTIR identifier = new IdentifierTIR("foo");
		
		expect( myApplicables.get(EasyMock.same(identifier))
			).andReturn( primitive );
		expect(primitive.apply(myCalculator))
		  .andReturn(result);
		
		myControl.replay();
		
		assertSame(result, myCalculator.visitApplication(new ApplicationTIR(identifier,
			    new TupleTIR())));
		
		myControl.verify();
	}
	
	@Test
	public void shouldCalculateApplicationWithArguments(){
		Applicable primitive = myControl.createMock(Applicable.class);
		HobbesTIR result = myControl.createMock(HobbesTIR.class);
		HobbesTIR[] arguments = myControl.createMocks(new HobbesTIR[3], HobbesTIR.class);
		IntegerTIR[] evaluatedArguments = new IntegerTIR[]{
				new IntegerTIR(23), new IntegerTIR(42), new IntegerTIR(665)
				};
		
		
		expect( arguments[0].accept(myCalculator) ).andReturn(evaluatedArguments[0]);
		expect( arguments[1].accept(myCalculator) ).andReturn(evaluatedArguments[1]);
		expect( arguments[2].accept(myCalculator) ).andReturn(evaluatedArguments[2]);
		
		expect( myApplicables.get(new IdentifierTIR("foo") )).andReturn( primitive );
		expect( primitive.apply(myCalculator, evaluatedArguments) ).andReturn(result);

		
		myControl.replay();
		
		assertSame(result, myCalculator.visitApplication(new ApplicationTIR(new IdentifierTIR("foo"),
			    new TupleTIR(arguments))));
		
		myControl.verify();		
	}
	
	@Test
	public void shouldCalculateIfWhenTrue() {
	    HobbesTIR condition = myControl.createMock(HobbesTIR.class);
	    HobbesTIR then = myControl.createMock(HobbesTIR.class);
	    HobbesTIR els = myControl.createMock(HobbesTIR.class);
	    HobbesTIR result = myControl.createMock(HobbesTIR.class);
	    IfTIR ife = new IfTIR(condition, then, els);
	    expect(condition.accept(myCalculator)).andReturn(new IntegerTIR(1));
	    expect(then.accept(myCalculator)).andReturn(result);
	    myControl.replay();
	    assertSame(result, myCalculator.visitIf(ife));
	    myControl.verify();
	}
	
	@Test
	public void shouldCalculateIfWhenFalse() {
	    HobbesTIR condition = myControl.createMock(HobbesTIR.class);
	    HobbesTIR then = myControl.createMock(HobbesTIR.class);
	    HobbesTIR els = myControl.createMock(HobbesTIR.class);
	    HobbesTIR result = myControl.createMock(HobbesTIR.class);
	    IfTIR ife = new IfTIR(condition, then, els);
	    expect(condition.accept(myCalculator)).andReturn(new TupleTIR());
	    expect(els.accept(myCalculator)).andReturn(result);
	    myControl.replay();
	    assertSame(result, myCalculator.visitIf(ife));
	    myControl.verify();
	}
	
	@Test
	public void shouldCalculateIdentifier(){
		HobbesTIR result = myControl.createMock(HobbesTIR.class);
		IdentifierTIR identifier = new IdentifierTIR("+");
		
		expect(myEnvironment.get(EasyMock.same(identifier))).andReturn(result);
		myControl.replay();
		
		assertSame(result, myCalculator.visitIdentifier(identifier));
		
		myControl.verify();
	}
	
	@SuppressWarnings("unchecked")
	@Test
	public void shouldCalculateLet(){
		HobbesTIR result = myControl.createMock(HobbesTIR.class);
		IdentifierTIR identifier = new IdentifierTIR("+");
		HobbesTIR initialization = myControl.createMock(HobbesTIR.class);
		HobbesTIR body = myControl.createMock(HobbesTIR.class);
		HobbesTIR value = myControl.createMock(HobbesTIR.class);
		IEnvironment<HobbesTIR> nestedEnvironment = myControl.createMock(IEnvironment.class);
		
		expect(initialization.accept(myCalculator)).andReturn(value);
		expect(myEnvironment.create()).andReturn(nestedEnvironment);
		nestedEnvironment.add(EasyMock.same(identifier), EasyMock.same(value));
		Calculator nestedCalculator = new Calculator(myCalculator.getPrimitives(), nestedEnvironment);
		expect(body.accept(nestedCalculator)).andReturn(result);


		myControl.replay();
		assertSame(result, myCalculator.visitLet(new LetTIR(identifier, initialization, body)));
		myControl.verify();
		
	}
	
	@SuppressWarnings("unchecked")
	@Test
	public void shouldCalculateLetFunction(){
		HobbesTIR result = myControl.createMock(HobbesTIR.class);
		
		IdentifierTIR lambdaIdent = new IdentifierTIR("h");
		HobbesTIR lambdaBody = myControl.createMock(HobbesTIR.class);
		LambdaTIR functionLambda = new LambdaTIR(lambdaIdent, lambdaBody);
		
		IdentifierTIR functionName = new IdentifierTIR("name");
		HobbesTIR body = myControl.createMock(HobbesTIR.class);
		LetFunctionTIR letFunction = new LetFunctionTIR(functionName, functionLambda, body);

		IEnvironment<Applicable> nextApplicables = myControl.createMock(IEnvironment.class);
		
		expect(myApplicables.create()).andReturn(nextApplicables);
		nextApplicables.add(EasyMock.same(functionName), EasyMock.eq(new ApplicableLambda(functionLambda)));
		Calculator nextCalculator = new Calculator(nextApplicables, myEnvironment);
		expect(body.accept(nextCalculator)).andReturn(result);
		
		myControl.replay();
		
		assertSame(result, myCalculator.visitLetFunction(letFunction));
		
		myControl.verify();
	}



}
